import {chrome, gecko, ie, mac, presto, safari, webkit} from "../util/browser.js"
import {e_preventDefault} from "../util/event.js"

import {updateDisplaySimple} from "./update_display.js"
import {setScrollLeft, updateScrollTop} from "./scrolling.js"

// Since the delta values reported on mouse wheel events are
// unstandardized between browsers and even browser versions, and
// generally horribly unpredictable, this code starts by measuring
// the scroll effect that the first few mouse wheel events have,
// and, from that, detects the way it can convert deltas to pixel
// offsets afterwards.
//
// The reason we want to know the amount a wheel event will scroll
// is that it gives us a chance to update the display before the
// actual scrolling happens, reducing flickering.

let wheelSamples = 0, wheelPixelsPerUnit = null
// Fill in a browser-detected starting value on browsers where we
// know one. These don't have to be accurate -- the result of them
// being wrong would just be a slight flicker on the first wheel
// scroll (if it is large enough).
if (ie) wheelPixelsPerUnit = -.53
else if (gecko) wheelPixelsPerUnit = 15
else if (chrome) wheelPixelsPerUnit = -.7
else if (safari) wheelPixelsPerUnit = -1 / 3

function wheelEventDelta(e) {
    let dx = e.wheelDeltaX, dy = e.wheelDeltaY
    if (dx == null && e.detail && e.axis == e.HORIZONTAL_AXIS) dx = e.detail
    if (dy == null && e.detail && e.axis == e.VERTICAL_AXIS) dy = e.detail
    else if (dy == null) dy = e.wheelDelta
    return {x: dx, y: dy}
}

export function wheelEventPixels(e) {
    let delta = wheelEventDelta(e)
    delta.x *= wheelPixelsPerUnit
    delta.y *= wheelPixelsPerUnit
    return delta
}

export function onScrollWheel(cm, e) {
    let delta = wheelEventDelta(e), dx = delta.x, dy = delta.y

    let display = cm.display, scroll = display.scroller
    // Quit if there's nothing to scroll here
    let canScrollX = scroll.scrollWidth > scroll.clientWidth
    let canScrollY = scroll.scrollHeight > scroll.clientHeight
    if (!(dx && canScrollX || dy && canScrollY)) return

    // Webkit browsers on OS X abort momentum scrolls when the target
    // of the scroll event is removed from the scrollable element.
    // This hack (see related code in patchDisplay) makes sure the
    // element is kept around.
    if (dy && mac && webkit) {
        outer: for (let cur = e.target, view = display.view; cur != scroll; cur = cur.parentNode) {
            for (let i = 0; i < view.length; i++) {
                if (view[i].node == cur) {
                    cm.display.currentWheelTarget = cur
                    break outer
                }
            }
        }
    }

    // On some browsers, horizontal scrolling will cause redraws to
    // happen before the gutter has been realigned, causing it to
    // wriggle around in a most unseemly way. When we have an
    // estimated pixels/delta value, we just handle horizontal
    // scrolling entirely here. It'll be slightly off from native, but
    // better than glitching out.
    if (dx && !gecko && !presto && wheelPixelsPerUnit != null) {
        if (dy && canScrollY)
            updateScrollTop(cm, Math.max(0, scroll.scrollTop + dy * wheelPixelsPerUnit))
        setScrollLeft(cm, Math.max(0, scroll.scrollLeft + dx * wheelPixelsPerUnit))
        // Only prevent default scrolling if vertical scrolling is
        // actually possible. Otherwise, it causes vertical scroll
        // jitter on OSX trackpads when deltaX is small and deltaY
        // is large (issue #3579)
        if (!dy || (dy && canScrollY))
            e_preventDefault(e)
        display.wheelStartX = null // Abort measurement, if in progress
        return
    }

    // 'Project' the visible viewport to cover the area that is being
    // scrolled into view (if we know enough to estimate it).
    if (dy && wheelPixelsPerUnit != null) {
        let pixels = dy * wheelPixelsPerUnit
        let top = cm.doc.scrollTop, bot = top + display.wrapper.clientHeight
        if (pixels < 0) top = Math.max(0, top + pixels - 50)
        else bot = Math.min(cm.doc.height, bot + pixels + 50)
        updateDisplaySimple(cm, {top: top, bottom: bot})
    }

    if (wheelSamples < 20) {
        if (display.wheelStartX == null) {
            display.wheelStartX = scroll.scrollLeft;
            display.wheelStartY = scroll.scrollTop
            display.wheelDX = dx;
            display.wheelDY = dy
            setTimeout(() => {
                if (display.wheelStartX == null) return
                let movedX = scroll.scrollLeft - display.wheelStartX
                let movedY = scroll.scrollTop - display.wheelStartY
                let sample = (movedY && display.wheelDY && movedY / display.wheelDY) ||
                    (movedX && display.wheelDX && movedX / display.wheelDX)
                display.wheelStartX = display.wheelStartY = null
                if (!sample) return
                wheelPixelsPerUnit = (wheelPixelsPerUnit * wheelSamples + sample) / (wheelSamples + 1)
                ++wheelSamples
            }, 200)
        } else {
            display.wheelDX += dx;
            display.wheelDY += dy
        }
    }
}
